/* $Id: SubmissionEntry.java 137 2011-08-29 20:33:36Z altdotua@gmail.com $ */

package alt.djudge.frontend.server.datatypes;

import java.util.Date;
import javax.cache.Cache;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import alt.djudge.frontend.server.datatypes.scores.AbstractScoreEntry;
import alt.djudge.frontend.server.datatypes.scores.ContestProblemScoreEntry;
import alt.djudge.frontend.server.datatypes.scores.ContestScoreEntry;
import alt.djudge.frontend.server.datatypes.scores.ContestUserScoreEntry;
import alt.djudge.frontend.server.datatypes.scores.ProblemUserScoreEntry;
import alt.djudge.frontend.server.datatypes.scores.UserScoreEntry;
import alt.djudge.frontend.server.models.CacheModel;
import alt.djudge.frontend.server.models.ContestsModel;
import alt.djudge.frontend.server.models.scores.ContestProblemScoreModel;
import alt.djudge.frontend.server.models.scores.ContestScoreModel;
import alt.djudge.frontend.server.models.scores.ContestUserScoreModel;
import alt.djudge.frontend.server.models.scores.ProblemUserScoreModel;
import alt.djudge.frontend.server.models.scores.UserScoreModel;
import alt.djudge.frontend.shared.dto.admin.AdminDTOEntry;
import alt.djudge.frontend.shared.dto.admin.SubmissionEntryDTO;


@PersistenceCapable(detachable="true")
public class SubmissionEntry extends AbstractEntry
{
	private static final long serialVersionUID = 1L;

	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	protected Long id;
	
	@Override
	public Long getId()
	{
		return id;
	}
	
	@Override
	public void setId(Long id)
	{
		this.id = id;
	}
	
	@Persistent
	private Long userId;

	@Persistent
	private Long contestProblemId;

	@Persistent
	private Long contestId;

	@Persistent
	private Long languageId;

	@Persistent
	private Long submissionDetailsId;

	@Persistent
	private Date submissionTime = new Date();
	
	@Persistent
	private Long contestTime = -1L;

	@Persistent
	private Integer score = -1;

	@Persistent
	private Integer wrongTestNumber = -1;

	@Persistent
	private Integer maxTimeUsed = -1;

	@Persistent
	private Boolean flagFirstTestOnly;

	@Persistent
	private Integer maxMemoryUsed = -1;

	@Persistent
	private Integer maxOutputGenerated = -1;

	@Persistent
	private String judgement = "N/A";

	/* DJudge-related data */
	@Persistent
	private Integer djudgeFlag = 0;

	@Persistent
	private Date djudgeFetchDate;

	@Persistent
	private Date djudgeCheckDate;

	@Persistent
	private Integer sourceSize;

	@Persistent
	private String ipAddress;
	
	@Persistent
	private Boolean virtual = false;

	public SubmissionEntry(SubmissionEntry dtoEntry)
	{
		// TODO Auto-generated constructor stub
	}

	public SubmissionEntry()
	{
		// TODO Auto-generated constructor stub
	}

	public Long getUserId()
	{
		return userId;
	}

	public void setUserId(Long userId)
	{
		this.userId = userId;
	}

	public Long getContestProblemId()
	{
		return contestProblemId;
	}

	public void setContestProblemId(Long problemId)
	{
		this.contestProblemId = problemId;
	}

	public Long getLanguageId()
	{
		return languageId;
	}

	public void setLanguageId(Long languageId)
	{
		this.languageId = languageId;
	}

	public Date getSubmissionTime()
	{
		return submissionTime;
	}

	public void setSubmissionTime(Date submissionTime)
	{
		this.submissionTime = submissionTime;
	}

	public Integer getScore()
	{
		return score;
	}

	public void setScore(Integer score)
	{
		this.score = score;
	}

	public Integer getWrongTestNumber()
	{
		return wrongTestNumber;
	}

	public void setWrongTestNumber(Integer wrongTestNumber)
	{
		this.wrongTestNumber = wrongTestNumber;
	}

	public Integer getMaxTimeUsed()
	{
		return maxTimeUsed;
	}

	public void setMaxTimeUsed(Integer maxTimeUsed)
	{
		this.maxTimeUsed = maxTimeUsed;
	}

	public Integer getMaxMemoryUsed()
	{
		return maxMemoryUsed;
	}

	public void setMaxMemoryUsed(Integer maxMemoryUsed)
	{
		this.maxMemoryUsed = maxMemoryUsed;
	}

	public String getJudgement()
	{
		return judgement;
	}

	public void setJudgement(String judgement)
	{
		this.judgement = judgement;
	}

	public Integer getDjudgeFlag()
	{
		return djudgeFlag;
	}

	public void setDjudgeFlag(Integer djudgeFlag)
	{
		this.djudgeFlag = djudgeFlag;
		if (djudgeFlag == 0)
		{
			judgement = "N/A";
			maxMemoryUsed = -1;
			maxTimeUsed = -1;
			wrongTestNumber = -1;
		}
	}

	public Date getDjudgeFetchDate()
	{
		return djudgeFetchDate;
	}

	public void setDjudgeFetchDate(Date djudgeFetchDate)
	{
		this.djudgeFetchDate = djudgeFetchDate;
	}

	public Date getDjudgeCheckDate()
	{
		return djudgeCheckDate;
	}

	public void setDjudgeCheckDate(Date djudgeCheckDate)
	{
		this.djudgeCheckDate = djudgeCheckDate;
	}

	public Long getSubmissionDetailsId()
	{
		return submissionDetailsId;
	}

	public void setSubmissionDetailsId(Long submissionDetailsId)
	{
		this.submissionDetailsId = submissionDetailsId;
	}

	public Boolean getFlagFirstTestOnly()
	{
		return flagFirstTestOnly;
	}

	public void setFlagFirstTestOnly(Boolean flagFirstTestOnly)
	{
		this.flagFirstTestOnly = flagFirstTestOnly;
	}

	public Integer getSourceSize()
	{
		return sourceSize;
	}

	public void setSourceSize(Integer sourceSize)
	{
		this.sourceSize = sourceSize;
	}

	public void setContestId(Long contestId)
	{
		this.contestId = contestId;
	}

	public Long getContestId()
	{
		return contestId;
	}

	public void setIpAddress(String ipAddress)
	{
		this.ipAddress = ipAddress;
	}

	public String getIpAddress()
	{
		return ipAddress;
	}

	public void prepareForRejudge()
	{
		djudgeFlag = 0;
		judgement = "N/A";
		wrongTestNumber = -1;
		maxMemoryUsed = -1;
		maxTimeUsed = -1;
		score = -1;
	}

	@Override
	public void clearCacheCustom()
	{
		Cache cache = CacheModel.getStaticCache();
		String keyContest = CacheModel.getContestScoreEntryKey(contestId);
		cache.remove(keyContest);
		
		String keyUser = CacheModel.getUserScoreEntryKey(userId);
		cache.remove(keyUser);
		
		String keyContestProblem = CacheModel.getContestProblemScoreEntryKey(
				contestId, contestProblemId);
		cache.remove(keyContestProblem);
		
		String keyContestUser = CacheModel.getContestUserScoreEntryKey(
				contestId, userId);
		cache.remove(keyContestUser);
		
		String keyProblemUser = CacheModel.getProblemUserScoreEntryKey(
				contestProblemId, userId);
		cache.remove(keyProblemUser);
		
		// ProblemUser statistics map 
		String key = CacheModel.getProblemUserScoreMapEntries(contestId, userId);
		cache.remove(key);
		
		String key2 = CacheModel.getContestProblemScoreMapEntries(contestId);
		cache.remove(key2);
		
		String key3 = CacheModel.getMonitorKey(contestId);
		cache.remove(key3);
	}

	public void addScores()
	{
		System.out.println("AddScore + " + getId());
		
		//log.debug("Scores : " + getId());
		/* Score entries */
		UserScoreEntry userScoreEntry = UserScoreModel.getUserEntry(userId);
		ContestScoreEntry contestScoreEntry = ContestScoreModel.getContestEntry(contestId);
		ContestProblemScoreEntry contestProblemScoreEntry = ContestProblemScoreModel.getContestProblemEntry(contestId, contestProblemId);
		ContestUserScoreEntry contestUserScoreEntry = ContestUserScoreModel.getContestUserEntry(contestId, userId);
		ProblemUserScoreEntry problemUserScoreEntry = ProblemUserScoreModel.getProblemUserEntry(contestProblemId, userId);

		AbstractScoreEntry[] scores = { userScoreEntry,
				contestScoreEntry, contestProblemScoreEntry,
				contestUserScoreEntry, problemUserScoreEntry };
		
		addScores(userScoreEntry, contestScoreEntry, contestProblemScoreEntry, contestUserScoreEntry, problemUserScoreEntry, true);
		
		/* Making persistent */
		for (int i = 0; i < 5; i++)
			scores[i].makePersistent();
	}

	public void addScores(UserScoreEntry userScoreEntry, ContestScoreEntry contestScoreEntry, ContestProblemScoreEntry contestProblemScoreEntry, ContestUserScoreEntry contestUserScoreEntry, ProblemUserScoreEntry problemUserScoreEntry, boolean flag)
	{
		System.out.println("AddScore + " + getId());
		
		AbstractScoreEntry[] scores = { userScoreEntry, 
				contestScoreEntry, contestProblemScoreEntry,
				contestUserScoreEntry, problemUserScoreEntry };
		
		ContestEntry contest = ContestsModel.getEntry(contestId);
		// TODO: change for virtual contest
		Long contestTime = (getSubmissionTime().getTime() - contest.getStartTime().getTime()) / 1000 / 60;
		
		for (int i = 0; i < 5; i++)
		{
			if (flag)
			{
				/* Decrement pending counters */
				scores[i].decrementPendingCount();
			}
			/* Incrementing active counters */
			scores[i].incrementActiveCount();
			/* Last submitted times */
			scores[i].setLastSubmittedDate(getSubmissionTime());
			scores[i].setLastSubmittedTime(contestTime);
			if ("AC".equals(getJudgement()))
				scores[i].incrementAcceptedCount();
			else
				scores[i].incrementRejectedCount();
		}
		
		if (!problemUserScoreEntry.getAccepted())
		{
			if (!contestProblemScoreEntry.getAccepted() && "AC".equals(getJudgement()))
			{
				contestProblemScoreEntry.setMaxScore(getScore());
				contestProblemScoreEntry.setMaxScoreFirstDate(getSubmissionTime());
				contestProblemScoreEntry.setMaxScoreFirstTime(contestTime);
				contestProblemScoreEntry.setMaxScoreId(getId());
			}
			for (int i = 0; i < 5; i++)
			{
				if ("AC".equals(getJudgement()))
				{
					scores[i].incrementUniqueAcceptedCount();
					scores[i].setLastAcceptedDate(getSubmissionTime());
					scores[i].setLastAcceptedTime(contestTime);
					scores[i].setRejectedBeforeAcceptedCount(scores[i].getRejectedBeforeAcceptedCount() +
							problemUserScoreEntry.getRejectedCount());
					scores[i].setTotalTime(scores[i].getTotalTime() + contestTime);
				}
				else
					scores[i].incrementUniqueRejectedCount();
			}
			if (getScore() > problemUserScoreEntry.getMaxScore())
			{
				for (int i = 0; i < 5; i++)
					scores[i].setTotalScore(scores[i].getTotalScore() - problemUserScoreEntry.getMaxScore() + getScore());
				problemUserScoreEntry.setMaxScore(getScore());
				problemUserScoreEntry.setMaxScoreFirstDate(getSubmissionTime());
				problemUserScoreEntry.setMaxScoreFirstTime(contestTime);
				problemUserScoreEntry.setMaxScoreId(getId());
			}
			if (getScore() > contestProblemScoreEntry.getMaxScore())
			{
				contestProblemScoreEntry.setMaxScore(getScore());
				contestProblemScoreEntry.setMaxScoreFirstDate(getSubmissionTime());
				contestProblemScoreEntry.setMaxScoreFirstTime(contestTime);
				contestProblemScoreEntry.setMaxScoreId(getId());
			}
		}
	}

	public void incrementPendingsCount()
	{
		/*
		if (!flagFirstTestOnly)
		{
			UserScoreEntry userScoreEntry = UserScoreModel.getUserEntry(userId);
			userScoreEntry.incrementPendingCount();
			userScoreEntry.makePersistent();

			ContestScoreEntry contestScoreEntry = ContestScoreModel
					.getContestEntry(contestId);
			contestScoreEntry.incrementPendingCount();
			contestScoreEntry.makePersistent();

			ContestProblemScoreEntry contestProblemScoreEntry = ContestProblemScoreModel
					.getContestProblemEntry(contestId, contestProblemId);
			contestProblemScoreEntry.incrementPendingCount();
			contestProblemScoreEntry.makePersistent();

			ContestUserScoreEntry contestUserScoreEntry = ContestUserScoreModel
					.getContestUserEntry(contestId, userId);
			contestUserScoreEntry.incrementPendingCount();
			contestUserScoreEntry.makePersistent();

			ProblemUserScoreEntry problemUserScoreEntry = ProblemUserScoreModel
					.getProblemUserEntry(contestProblemId, userId);
			problemUserScoreEntry.incrementPendingCount();
			problemUserScoreEntry.makePersistent();
		}*/
	}

	public void decrementPendingsCount()
	{
		/*UserScoreEntry userScoreEntry = UserScoreModel.getUserEntry(userId);
		userScoreEntry.decrementPendingCount();
		userScoreEntry.makePersistent();

		ContestScoreEntry contestScoreEntry = ContestScoreModel
				.getContestEntry(contestId);
		contestScoreEntry.decrementPendingCount();
		contestScoreEntry.makePersistent();

		ContestProblemScoreEntry contestProblemScoreEntry = ContestProblemScoreModel
				.getContestProblemEntry(contestId, contestProblemId);
		contestProblemScoreEntry.decrementPendingCount();
		contestProblemScoreEntry.makePersistent();

		ContestUserScoreEntry contestUserScoreEntry = ContestUserScoreModel
				.getContestUserEntry(contestId, userId);
		contestUserScoreEntry.decrementPendingCount();
		contestUserScoreEntry.makePersistent();

		ProblemUserScoreEntry problemUserScoreEntry = ProblemUserScoreModel
				.getProblemUserEntry(contestProblemId, userId);
		problemUserScoreEntry.decrementPendingCount();
		problemUserScoreEntry.makePersistent();*/
	}

	public void setVirtual(Boolean virtual)
	{
		this.virtual = virtual;
	}

	public Boolean getVirtual()
	{
		return virtual != null ? virtual : false;
	}

	public void setMaxOutputGenerated(Integer maxOutputGenerated)
	{
		this.maxOutputGenerated = maxOutputGenerated;
	}

	public Integer getMaxOutputGenerated()
	{
		return maxOutputGenerated;
	}

	public boolean isPending()
	{
		return djudgeFlag <= 0;
	}

	public boolean isAccepted()
	{
		return "AC".equalsIgnoreCase(judgement);
	}

	public Long getContestTime()
	{
		return contestTime;
	}
	
	public void setContestTime(Long contestTime)
	{
		this.contestTime = contestTime;
	}
	
	@Override
	public boolean deletePersistent()
	{
		// TODO: delete all related data
		return super.deletePersistent();
	}

	public boolean isWrongAnswer()
	{
		return "WA".equalsIgnoreCase(judgement);
	}

	public boolean isCompilationError()
	{
		return "CE".equalsIgnoreCase(judgement);
	}

	public boolean isMemoryLimitExceeded()
	{
		return "MLE".equalsIgnoreCase(judgement);
	}

	public boolean isTimeLimitExceeded()
	{
		return "TLE".equalsIgnoreCase(judgement);
	}

	public boolean isRuntimeError()
	{
		return "RE".equalsIgnoreCase(judgement);
	}

	@Override
	public void fromDTO(AdminDTOEntry dtoEntry)
	{
		
	}
	
	@Override
	public SubmissionEntryDTO ownAdminDTO()
	{
		String[] data = new String[] {
				getId().toString(),
				getSubmissionTime().toString(),
				getContestId().toString(),
				getUserId().toString(),
				getLanguageId().toString(),
				getJudgement().toString(),
		};
		return new SubmissionEntryDTO(data);
	}
}
